/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.delegate.event.impl;

import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.ActivitiIllegalArgumentException;
import com.je.bpm.engine.delegate.event.ActivitiEvent;
import com.je.bpm.engine.delegate.event.ActivitiEventListener;
import com.je.bpm.engine.delegate.event.ActivitiEventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Class that allows adding and removing event listeners and dispatching events to the appropriate listeners.
 * 该类允许添加和删除事件侦听器并将事件分派到相应的侦听器。
 */
public class ActivitiEventSupport {

    private static final Logger LOG = LoggerFactory.getLogger(ActivitiEventSupport.class);

    protected List<ActivitiEventListener> eventListeners;
    protected Map<ActivitiEventType, List<ActivitiEventListener>> typedListeners;

    public ActivitiEventSupport() {
        eventListeners = new CopyOnWriteArrayList();
        typedListeners = new HashMap();
    }

    public synchronized void addEventListener(ActivitiEventListener listenerToAdd) {
        if (listenerToAdd == null) {
            throw new ActivitiIllegalArgumentException("Listener cannot be null.");
        }
        if (!eventListeners.contains(listenerToAdd)) {
            eventListeners.add(listenerToAdd);
        }
    }

    public synchronized void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... types) {
        if (listenerToAdd == null) {
            throw new ActivitiIllegalArgumentException("Listener cannot be null.");
        }
        if (types == null || types.length == 0) {
            addEventListener(listenerToAdd);
        } else {
            for (ActivitiEventType type : types) {
                addTypedEventListener(listenerToAdd, type);
            }
        }
    }

    public void removeEventListener(ActivitiEventListener listenerToRemove) {
        eventListeners.remove(listenerToRemove);
        for (List<ActivitiEventListener> listeners : typedListeners.values()) {
            listeners.remove(listenerToRemove);
        }
    }

    public void dispatchEvent(ActivitiEvent event) {
        if (event == null) {
            throw new ActivitiIllegalArgumentException("Event cannot be null.");
        }
        if (event.getType() == null) {
            throw new ActivitiIllegalArgumentException("Event type cannot be null.");
        }
        // Call global listeners
        if (!eventListeners.isEmpty()) {
            for (ActivitiEventListener listener : eventListeners) {
                dispatchEvent(event, listener);
            }
        }
        // Call typed listeners, if any
        List<ActivitiEventListener> typed = typedListeners.get(event.getType());
        if (typed != null && !typed.isEmpty()) {
            for (ActivitiEventListener listener : typed) {
                dispatchEvent(event, listener);
            }
        }
    }

    protected void dispatchEvent(ActivitiEvent event, ActivitiEventListener listener) {
        try {
            listener.onEvent(event);
        } catch (Throwable t) {
            if (listener.isFailOnException()) {
                throw new ActivitiException("Exception while executing event-listener", t);
            } else {
                // Ignore the exception and continue notifying remaining listeners. The listener
                // explicitly states that the exception should not bubble up
                LOG.warn("Exception while executing event-listener, which was ignored", t);
            }
        }
    }

    protected synchronized void addTypedEventListener(ActivitiEventListener listener, ActivitiEventType type) {
        List<ActivitiEventListener> listeners = typedListeners.get(type);
        if (listeners == null) {
            // Add an empty list of listeners for this type
            listeners = new CopyOnWriteArrayList<ActivitiEventListener>();
            typedListeners.put(type, listeners);
        }

        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }
}
